#pragma once

#include <memory>
#include <vector>
#include <string>

using String = std::string;

template <typename T>
using Vector = std::vector<T>;

template <typename T>
using Rc = std::shared_ptr<T>;

enum class ModuleKind
{
    // Broadcaster,
    Flipflop,
    Conjunction,
    Output,
};

enum class ModuleState
{
    Low,
    High,
    Init,
};

class Module
{
public:
    Module(String name) :
        _name{std::move(name)}, 
        _state{ModuleState::Init},
        _inputs{},
        _outputs{} 
    {
    }

public:
    virtual
    auto update() -> bool = 0;

    virtual
    auto complete_name() -> String = 0;

public:
    auto add_output(Rc<Module> module) -> void;
    auto add_intput(Rc<Module> module) -> void;
    auto print() -> void;

protected:
    auto show_pulse_outputs() -> void;

public:
    auto outputs() const -> const Vector<Rc<Module>>&
    { return this->_outputs; }
    
    auto state() const -> ModuleState 
    { return this->_state; }

    auto name() const -> const String&
    { return this->_name; }

protected:
    String _name;
    ModuleState _state;

    Vector<Rc<Module>> _inputs;
    Vector<Rc<Module>> _outputs;
};

// class Broadcaster : public Module
// {
// public:
//     Broadcaster(String name) :
//         Module{std::move(name)} {}
    
// public:
//     virtual
//     auto update() -> bool override;

//     virtual
//     auto complete_name() -> String override;
// };

enum class FlipflopState
{
    Off,
    On
};

class Flipflop : public Module 
{
public:
    Flipflop(String name) : 
        Module{std::move(name)},
        _ffstate{FlipflopState::Off} {}
    
public:
    virtual 
    auto update() -> bool override;

    virtual
    auto complete_name() -> String override;

public:
    auto force_update(ModuleState state) -> bool;

private:
    auto input() -> Rc<Module>
    { return this->_inputs.front(); }

private:
    FlipflopState _ffstate;
};

class Conjunction : public Module 
{
public:
    Conjunction(String name) : 
        Module{std::move(name)}, _init{true} {}
    
public:
    virtual 
    auto update() -> bool override;

    virtual
    auto complete_name() -> String override;

private:
    bool _init;
};

class Output : public Module 
{
public:
    Output(String name) : 
        Module{std::move(name)} {}
    
public:
    virtual 
    auto update() -> bool override;

    virtual
    auto complete_name() -> String override;
};